/**
 * elancom Corporation copy right 2012 - 2015  
 *   
 * $Header: https://192.168.1.224/svn/elancom/product/elancom-platform/trunk/src/main/java/com/elancom/framework/commons/json/JSON.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Id: JSON.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Author: lanzhouxue $
 * $Date: 2012-07-02 11:09:12 +0800 (Mon, 02 Jul 2012) $
 * $Revision: 1034 $
 */
package com.elancom.framework.commons.json;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import com.elancom.framework.commons.json.parser.DefaultJSONParser;
import com.elancom.framework.commons.json.parser.DefaultJSONParser.ResolveTask;
import com.elancom.framework.commons.json.parser.Feature;
import com.elancom.framework.commons.json.parser.JSONLexer;
import com.elancom.framework.commons.json.parser.JSONToken;
import com.elancom.framework.commons.json.parser.ParserConfig;
import com.elancom.framework.commons.json.parser.deserializer.FieldDeserializer;
import com.elancom.framework.commons.json.serializer.JSONSerializer;
import com.elancom.framework.commons.json.serializer.SerializeConfig;
import com.elancom.framework.commons.json.serializer.SerializeWriter;
import com.elancom.framework.commons.json.serializer.SerializerFeature;
import com.elancom.framework.commons.json.util.FieldInfo;
import com.elancom.framework.commons.json.util.IOUtils;
import com.elancom.framework.commons.json.util.ThreadLocalCache;
import com.elancom.framework.commons.json.util.TypeUtils;

/**
 * @author elancom
 */
public abstract class JSON implements JSONStreamAware, JSONAware {

	public static int DEFAULT_PARSER_FEATURE;
	static {
		int features = 0;
		features |= Feature.AutoCloseSource.getMask();
		features |= Feature.InternFieldNames.getMask();
		features |= Feature.UseBigDecimal.getMask();
		features |= Feature.AllowUnQuotedFieldNames.getMask();
		features |= Feature.AllowSingleQuotes.getMask();
		features |= Feature.AllowArbitraryCommas.getMask();
		features |= Feature.SortFeidFastMatch.getMask();
		features |= Feature.IgnoreNotMatch.getMask();
		DEFAULT_PARSER_FEATURE = features;
	}

	public static String DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

	public static int DEFAULT_GENERATE_FEATURE;
	static {
		int features = 0;
		features |= com.elancom.framework.commons.json.serializer.SerializerFeature.QuoteFieldNames.getMask();
		features |= com.elancom.framework.commons.json.serializer.SerializerFeature.SkipTransientField.getMask();
		features |= com.elancom.framework.commons.json.serializer.SerializerFeature.WriteEnumUsingToString.getMask();
		features |= com.elancom.framework.commons.json.serializer.SerializerFeature.SortField.getMask();
		// features |= com.elancom.framework.commons.json.serializer.SerializerFeature.WriteTabAsSpecial.getMask();
		// features |= com.elancom.framework.commons.json.serializer.SerializerFeature.WriteSlashAsSpecial.getMask();
		DEFAULT_GENERATE_FEATURE = features;
	}

	public static final Object parse(String text) {
		return parse(text, DEFAULT_PARSER_FEATURE);
	}

	public static final Object parse(String text, int features) {
		if (text == null) {
			return null;
		}

		final DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
		final Object value = parser.parse();

		handleResovleTask(parser, value);

		parser.close();

		return value;
	}

	public static final Object parse(byte[] input, Feature... features) {
		return parse(input, 0, input.length, ThreadLocalCache.getUTF8Decoder(), features);
	}

	public static final Object parse(byte[] input, int off, int len, CharsetDecoder charsetDecoder, Feature... features) {
		if (input == null || input.length == 0) {
			return null;
		}

		int featureValues = DEFAULT_PARSER_FEATURE;
		for (final Feature featrue : features) {
			featureValues = Feature.config(featureValues, featrue, true);
		}

		return parse(input, off, len, charsetDecoder, featureValues);
	}

	public static final Object parse(byte[] input, int off, int len, CharsetDecoder charsetDecoder, int features) {
		charsetDecoder.reset();

		final int scaleLength = (int) (len * (double) charsetDecoder.maxCharsPerByte());
		final char[] chars = ThreadLocalCache.getChars(scaleLength);

		final ByteBuffer byteBuf = ByteBuffer.wrap(input, off, len);
		final CharBuffer charBuf = CharBuffer.wrap(chars);
		IOUtils.decode(charsetDecoder, byteBuf, charBuf);

		final int position = charBuf.position();

		final DefaultJSONParser parser = new DefaultJSONParser(chars, position, ParserConfig.getGlobalInstance(), features);
		final Object value = parser.parse();

		handleResovleTask(parser, value);

		parser.close();

		return value;
	}

	public static final Object parse(String text, Feature... features) {
		int featureValues = DEFAULT_PARSER_FEATURE;
		for (final Feature featrue : features) {
			featureValues = Feature.config(featureValues, featrue, true);
		}

		return parse(text, featureValues);
	}

	public static final JSONObject parseObject(String text, Feature... features) {
		return (JSONObject) parse(text, features);
	}

	public static final JSONObject parseObject(String text) {
		final Object obj = parse(text);
		if (obj instanceof JSONObject) {
			return (JSONObject) obj;
		}

		return (JSONObject) JSON.toJSON(obj);
	}

	@SuppressWarnings("unchecked")
	public static final <T> T parseObject(String text, TypeReference<T> type, Feature... features) {
		return (T) parseObject(text, type.getType(), ParserConfig.getGlobalInstance(), DEFAULT_PARSER_FEATURE, features);
	}

	@SuppressWarnings("unchecked")
	public static final <T> T parseObject(String text, Class<T> clazz, Feature... features) {
		return (T) parseObject(text, (Type) clazz, ParserConfig.getGlobalInstance(), DEFAULT_PARSER_FEATURE, features);
	}

	@SuppressWarnings("unchecked")
	public static final <T> T parseObject(String input, Type clazz, Feature... features) {
		return (T) parseObject(input, clazz, ParserConfig.getGlobalInstance(), DEFAULT_PARSER_FEATURE, features);
	}

	@SuppressWarnings("unchecked")
	public static final <T> T parseObject(String input, Type clazz, int featureValues, Feature... features) {
		if (input == null) {
			return null;
		}

		for (final Feature featrue : features) {
			featureValues = Feature.config(featureValues, featrue, true);
		}

		final DefaultJSONParser parser = new DefaultJSONParser(input, ParserConfig.getGlobalInstance(), featureValues);
		final T value = (T) parser.parseObject(clazz);

		handleResovleTask(parser, value);

		parser.close();

		return value;
	}

	@SuppressWarnings("unchecked")
	public static final <T> T parseObject(String input, Type clazz, ParserConfig config, int featureValues, Feature... features) {
		if (input == null) {
			return null;
		}

		for (final Feature featrue : features) {
			featureValues = Feature.config(featureValues, featrue, true);
		}

		final DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
		final T value = (T) parser.parseObject(clazz);

		handleResovleTask(parser, value);

		parser.close();

		return value;
	}

	public static <T> int handleResovleTask(DefaultJSONParser parser, T value) {
		if (parser.isEnabled(Feature.DisableCircularReferenceDetect)) {
			return 0;
		}

		final int size = parser.getResolveTaskList().size();
		for (int i = 0; i < size; ++i) {
			final ResolveTask task = parser.getResolveTaskList().get(i);
			final FieldDeserializer fieldDeser = task.getFieldDeserializer();

			Object object = null;
			if (task.getOwnerContext() != null) {
				object = task.getOwnerContext().getObject();
			}

			final String ref = task.getReferenceValue();
			Object refValue;
			if (ref.startsWith("$")) {
				refValue = parser.getObject(ref);
			} else {
				refValue = task.getContext().getObject();
			}
			fieldDeser.setValue(object, refValue);
		}

		return size;
	}

	@SuppressWarnings("unchecked")
	public static final <T> T parseObject(byte[] input, Type clazz, Feature... features) {
		return (T) parseObject(input, 0, input.length, ThreadLocalCache.getUTF8Decoder(), clazz, features);
	}

	@SuppressWarnings("unchecked")
	public static final <T> T parseObject(byte[] input, int off, int len, CharsetDecoder charsetDecoder, Type clazz, Feature... features) {
		charsetDecoder.reset();

		final int scaleLength = (int) (len * (double) charsetDecoder.maxCharsPerByte());
		final char[] chars = ThreadLocalCache.getChars(scaleLength);

		final ByteBuffer byteBuf = ByteBuffer.wrap(input, off, len);
		final CharBuffer charByte = CharBuffer.wrap(chars);
		IOUtils.decode(charsetDecoder, byteBuf, charByte);

		final int position = charByte.position();

		return (T) parseObject(chars, position, clazz, features);
	}

	@SuppressWarnings("unchecked")
	public static final <T> T parseObject(char[] input, int length, Type clazz, Feature... features) {
		if (input == null || input.length == 0) {
			return null;
		}

		int featureValues = DEFAULT_PARSER_FEATURE;
		for (final Feature featrue : features) {
			featureValues = Feature.config(featureValues, featrue, true);
		}

		final DefaultJSONParser parser = new DefaultJSONParser(input, length, ParserConfig.getGlobalInstance(), featureValues);
		final T value = (T) parser.parseObject(clazz);

		handleResovleTask(parser, value);

		parser.close();

		return value;
	}

	public static final <T> T parseObject(String text, Class<T> clazz) {
		return parseObject(text, clazz, new Feature[0]);
	}

	public static final JSONArray parseArray(String text) {
		if (text == null) {
			return null;
		}

		final DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance());

		JSONArray array;

		final JSONLexer lexer = parser.getLexer();
		if (lexer.token() == JSONToken.NULL) {
			lexer.nextToken();
			array = null;
		} else if (lexer.token() == JSONToken.EOF) {
			array = null;
		} else {
			array = new JSONArray();
			parser.parseArray(array);

			handleResovleTask(parser, array);
		}

		parser.close();

		return array;
	}

	public static final <T> List<T> parseArray(String text, Class<T> clazz) {
		if (text == null) {
			return null;
		}

		List<T> list;

		final DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance());
		final JSONLexer lexer = parser.getLexer();
		if (lexer.token() == JSONToken.NULL) {
			lexer.nextToken();
			list = null;
		} else {
			list = new ArrayList<T>();
			parser.parseArray(clazz, list);

			handleResovleTask(parser, list);
		}

		parser.close();

		return list;
	}

	public static final List<Object> parseArray(String text, Type[] types) {
		if (text == null) {
			return null;
		}

		List<Object> list;

		final DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance());
		final Object[] objectArray = parser.parseArray(types);
		if (objectArray == null) {
			list = null;
		} else {
			list = Arrays.asList(objectArray);
		}

		handleResovleTask(parser, list);

		parser.close();

		return list;
	}

	// ======================

	public static final String toJSONString(Object object) {
		return toJSONString(object, new SerializerFeature[0]);
	}

	public static final String toJSONString(Object object, SerializerFeature... features) {
		final SerializeWriter out = new SerializeWriter();

		try {
			final JSONSerializer serializer = new JSONSerializer(out);
			for (final com.elancom.framework.commons.json.serializer.SerializerFeature feature : features) {
				serializer.config(feature, true);
			}

			serializer.write(object);

			return out.toString();
		} finally {
			out.close();
		}
	}

	/**
	 * @since 1.1.14
	 */
	public static final String toJSONStringWithDateFormat(Object object, String dateFormat, SerializerFeature... features) {
		final SerializeWriter out = new SerializeWriter();

		try {
			final JSONSerializer serializer = new JSONSerializer(out);
			for (final com.elancom.framework.commons.json.serializer.SerializerFeature feature : features) {
				serializer.config(feature, true);
			}

			serializer.config(SerializerFeature.WriteDateUseDateFormat, true);

			if (dateFormat != null) {
				serializer.setDateFormat(dateFormat);
			}

			serializer.write(object);

			return out.toString();
		} finally {
			out.close();
		}
	}

	public static final byte[] toJSONBytes(Object object, SerializerFeature... features) {
		final SerializeWriter out = new SerializeWriter();

		try {
			final JSONSerializer serializer = new JSONSerializer(out);
			for (final com.elancom.framework.commons.json.serializer.SerializerFeature feature : features) {
				serializer.config(feature, true);
			}

			serializer.write(object);

			return out.toBytes("UTF-8");
		} finally {
			out.close();
		}
	}

	public static final String toJSONString(Object object, SerializeConfig config, SerializerFeature... features) {
		final SerializeWriter out = new SerializeWriter();

		try {
			final JSONSerializer serializer = new JSONSerializer(out, config);
			for (final com.elancom.framework.commons.json.serializer.SerializerFeature feature : features) {
				serializer.config(feature, true);
			}

			serializer.write(object);

			return out.toString();
		} finally {
			out.close();
		}
	}

	public static final String toJSONStringZ(Object object, SerializeConfig mapping, SerializerFeature... features) {
		final SerializeWriter out = new SerializeWriter(features);

		try {
			final JSONSerializer serializer = new JSONSerializer(out, mapping);

			serializer.write(object);

			return out.toString();
		} finally {
			out.close();
		}
	}

	public static final byte[] toJSONBytes(Object object, SerializeConfig config, SerializerFeature... features) {
		final SerializeWriter out = new SerializeWriter();

		try {
			final JSONSerializer serializer = new JSONSerializer(out, config);
			for (final com.elancom.framework.commons.json.serializer.SerializerFeature feature : features) {
				serializer.config(feature, true);
			}

			serializer.write(object);

			return out.toBytes("UTF-8");
		} finally {
			out.close();
		}
	}

	public static final String toJSONString(Object object, boolean prettyFormat) {
		if (!prettyFormat) {
			return toJSONString(object);
		}

		return toJSONString(object, SerializerFeature.PrettyFormat);
	}

	// ======================================

	@Override
	public String toString() {
		return toJSONString();
	}

	@Override
	public String toJSONString() {
		final SerializeWriter out = new SerializeWriter();
		try {
			new JSONSerializer(out).write(this);
			return out.toString();
		} finally {
			out.close();
		}
	}

	@Override
	public void writeJSONString(Appendable appendable) {
		final SerializeWriter out = new SerializeWriter();
		try {
			new JSONSerializer(out).write(this);
			appendable.append(out.toString());
		} catch (final IOException e) {
			throw new JSONException(e.getMessage(), e);
		} finally {
			out.close();
		}
	}

	// ///////

	public static final Object toJSON(Object javaObject) {
		return toJSON(javaObject, ParserConfig.getGlobalInstance());
	}

	@SuppressWarnings("unchecked")
	public static final Object toJSON(Object javaObject, ParserConfig mapping) {
		if (javaObject == null) {
			return null;
		}

		if (javaObject instanceof JSON) {
			return javaObject;
		}

		if (javaObject instanceof Map) {
			final Map<Object, Object> map = (Map<Object, Object>) javaObject;

			final JSONObject json = new JSONObject(map.size());

			for (final Map.Entry<Object, Object> entry : map.entrySet()) {
				final Object key = entry.getKey();
				final String jsonKey = TypeUtils.castToString(key);
				final Object jsonValue = toJSON(entry.getValue());
				json.put(jsonKey, jsonValue);
			}

			return json;
		}

		if (javaObject instanceof Collection) {
			final Collection<Object> collection = (Collection<Object>) javaObject;

			final JSONArray array = new JSONArray(collection.size());

			for (final Object item : collection) {
				final Object jsonValue = toJSON(item);
				array.add(jsonValue);
			}

			return array;
		}

		final Class<?> clazz = javaObject.getClass();

		if (clazz.isEnum()) {
			return ((Enum<?>) javaObject).name();
		}

		if (clazz.isArray()) {
			final int len = Array.getLength(javaObject);

			final JSONArray array = new JSONArray(len);

			for (int i = 0; i < len; ++i) {
				final Object item = Array.get(javaObject, i);
				final Object jsonValue = toJSON(item);
				array.add(jsonValue);
			}

			return array;
		}

		if (mapping.isPrimitive(clazz)) {
			return javaObject;
		}

		try {
			final List<FieldInfo> getters = TypeUtils.computeGetters(clazz, null);

			final JSONObject json = new JSONObject(getters.size());
			for (final FieldInfo field : getters) {
				final Object value = field.get(javaObject);
				final Object jsonValue = toJSON(value);

				json.put(field.getName(), jsonValue);
			}

			return json;
		} catch (final Exception e) {
			// String msg = null;
			// if (f != null) {
			// msg = "转换JSON出错,类名：" + javaObject.getClass().getSimpleName() + ", 字段名：" + f.getName() + ", 方法：" + f.getMethod() + ", 字段类型：" + f.getFieldType();
			// } else {
			// msg = "转换JSON出错";
			// }
			throw new JSONException("转换JSON错误", e);
		}
	}

	public static final <T> T toJavaObject(JSON json, Class<T> clazz) {
		return TypeUtils.cast(json, clazz, ParserConfig.getGlobalInstance());
	}

	public final static String VERSION = "1.1.15";
}
